package spatialindex;

import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.LineSegment;

import util.MathUtil;

public class Circle implements IShape {

	public double[] center;
	public double radius;

	public Circle(double[] center, double radius) {
		this.center = center;
		this.radius = radius;
	}

	public double getRadius() {
		return radius;
	}

	public void setRadius(double radius) {
		this.radius = radius;
	}

	public void setCenter(double[] center) {
		this.center = center;
	}

	@Override
	public boolean contains(IShape s) {
		if (s instanceof Region)
			return contains((Region) s);

		if (s instanceof Point)
			return contains((Point) s);

		if (s instanceof Circle)
			return contains((Point) s);

		throw new IllegalStateException("contains: Not implemented yet!");

	}

	/**
	 * four corner points of the region are inside the circle.
	 * Caution: 2D geometry
	 * 
	 * @param r
	 * @return true if this circle contains the region
	 */
	public boolean contains(final Region r) {
		if (center.length != r.m_pLow.length)
			throw new IllegalArgumentException(
					"contains: Shape has the wrong number of dimensions.");

		double[] lefthigh = { r.m_pLow[0], r.m_pHigh[1] };
		double[] rightlow = { r.m_pHigh[0], r.m_pLow[1] };

		double d1 = MathUtil.computeEuclideanDistance(center, r.m_pLow);
		double d2 = MathUtil.computeEuclideanDistance(center, r.m_pHigh);
		double d3 = MathUtil.computeEuclideanDistance(center, lefthigh);
		double d4 = MathUtil.computeEuclideanDistance(center, rightlow);

		if (d1 > radius || d2 > radius || d3 > radius || d4 > radius) {
			return false;
		}
		return true;
	}

	public boolean contains(final Point p) {
		if (center.length != p.getDimension())
			throw new IllegalArgumentException(
					"contains: Shape has the wrong number of dimensions.");
		double d = MathUtil.computeEuclideanDistance(center, p.m_pCoords);
		return (d <= radius);
	}

	@Override
	public double getArea() {
		return Math.PI * radius * radius;
	}

	@Override
	public double[] getCenter() {
		return center;
	}

	@Override
	public long getDimension() {
		return center.length;
	}

	@Override
	public Region getMBR() {
		double[] leftlow = new double[center.length];
		double[] righthigh = new double[center.length];

		for (int cIndex = 0; cIndex < center.length; cIndex++) {
			leftlow[cIndex] = center[cIndex] - radius;
		}

		for (int cIndex = 0; cIndex < center.length; cIndex++) {
			righthigh[cIndex] = center[cIndex] + radius;
		}

		return new Region(leftlow, righthigh);
	}

	@Override
	public double getMinimumDistance(IShape s) {
		if (s instanceof Region)
			return getMinimumDistance((Region) s);

		if (s instanceof Point)
			return getMinimumDistance((Point) s);

		if (s instanceof Circle)
			return getMinimumDistance((Circle) s);

		return 0;
	}

	public double getMinimumDistance(final Circle c) {
		if (center.length != c.center.length)
			throw new IllegalArgumentException(
					"getMinimumDistance: Shape has the wrong number of dimensions.");

		return MathUtil.computeEuclideanDistance(center, c.center) - radius
				- c.radius;
	}

	public double getMinimumDistance(final Point p) {
		if (center.length != p.m_pCoords.length)
			throw new IllegalArgumentException(
					"getMinimumDistance: Shape has the wrong number of dimensions.");

		return MathUtil.computeEuclideanDistance(center, p.m_pCoords) - radius;
	}

	/**
	 * Pre-cond: r and this circle does not intersect
	 * Caution: constrained to 2D because of JTS linesegment
	 * 
	 * @param r
	 * @return
	 */
	public double getMinimumDistance(final Region r) {
		if (center.length != r.m_pLow.length)
			throw new IllegalArgumentException(
					"getMinimumDistance: Shape has the wrong number of dimensions.");

		double[] leftlow = r.m_pLow;
		double[] righthigh = r.m_pHigh;
		double[] lefthigh = { r.m_pLow[0], r.m_pHigh[1] };
		double[] rightlow = { r.m_pHigh[0], r.m_pLow[1] };

		LineSegment ls1 = new LineSegment(
				new Coordinate(leftlow[0], leftlow[1]), new Coordinate(
						lefthigh[0], lefthigh[1]));

		LineSegment ls2 = new LineSegment(
				new Coordinate(leftlow[0], leftlow[1]), new Coordinate(
						rightlow[0], rightlow[1]));

		LineSegment ls3 = new LineSegment(new Coordinate(righthigh[0],
				righthigh[1]), new Coordinate(rightlow[0], rightlow[1]));

		LineSegment ls4 = new LineSegment(new Coordinate(righthigh[0],
				righthigh[1]), new Coordinate(lefthigh[0], lefthigh[1]));

		// calculate distances to the four segments and take the least
		double ret = Double.MAX_VALUE;
		Coordinate cc = new Coordinate(center[0], center[1]);

		double d1 = ls1.distance(cc) - radius;
		double d2 = ls2.distance(cc) - radius;
		double d3 = ls3.distance(cc) - radius;
		double d4 = ls4.distance(cc) - radius;

		return min(d1, d2, d3, d4);
	}

	private double min(double d1, double d2, double d3, double d4) {
		double minsofar = Math.min(d1, d2);
		minsofar = Math.min(minsofar, d3);
		minsofar = Math.min(minsofar, d4);
		return minsofar;
	}

	@Override
	public boolean intersects(IShape s) {
		if (s instanceof Region)
			return intersects((Region) s);

		if (s instanceof Point)
			return contains((Point) s);

		if (s instanceof Circle)
			return intersects((Circle) s);

		throw new IllegalStateException("intersects: Not implemented yet!");
	}

	public boolean intersects(final Region r) {
		double minDist = getMinimumDistance(r) + radius;
		return minDist <= radius;
	}

	public boolean intersects(final Circle c) {
		if (center.length != c.center.length)
			throw new IllegalArgumentException(
					"intersects: Shape has the wrong number of dimensions.");

		double minDist = MathUtil.computeEuclideanDistance(center, c.center);
		return minDist <= radius + c.radius;
	}

	@Override
	public boolean touches(IShape s) {
		if (s instanceof Region)
			return touches((Region) s);

		if (s instanceof Point)
			return touches((Point) s);

		if (s instanceof Circle)
			return touches((Circle) s);

		throw new IllegalStateException("touches: Not implemented yet!");
	}

	public boolean touches(final Region r) {
		if (center.length != r.m_pLow.length)
			throw new IllegalArgumentException(
					"touches: Shape has the wrong number of dimensions.");

		return getMBR().touches(r);
	}

	public boolean touches(final Point p) {
		if (center.length != p.m_pCoords.length)
			throw new IllegalArgumentException(
					"touches: Shape has the wrong number of dimensions.");
		return getMBR().touches(p);
	}

	public boolean touches(final Circle c) {
		if (center.length != c.center.length)
			throw new IllegalArgumentException(
					"touches: Shape has the wrong number of dimensions.");

		return getMBR().touches(c.getMBR());
	}

}
